#!/usr/bin/env python

from optparse import OptionParser
from os import chdir, listdir, makedirs, sep
from os.path import abspath, basename, dirname, isdir, isfile, join
from shutil import copytree, rmtree
from subprocess import PIPE, Popen, call
from sys import argv, exit, stdout
from tempfile import mkdtemp

from util import (
    MAJOR_VERSION,
    MINOR_VERSION,
    PLATFORM_MACX,
    REVISION,
    getPlatform,
    writeTemplate
)

def copyLibraries(binaryFile, installPath, libDestinationDir,
                  ignoreDirectories=[], executablePath=None, dependencyMap={}):
    if executablePath is None:
        executablePath = binaryFile
    ignoreDirectories = [(abspath(d) + sep) for d in ignoreDirectories]
    for dependency in getDependencies(binaryFile):
        dependencyPath = join(executablePath, dependency)
        if any((dependencyPath.startswith(d) for d in ignoreDirectories)):
            continue
        libFile = basename(dependency)
        if libFile == basename(binaryFile):
            continue

        # The dependency isn't a self-reference.  See if it's in the dependency
        # map.
        location = dependencyMap.get(libFile)
        if location is None:

            # We'll infer the new location of the dependency.
            destinationPath = join(libDestinationDir, libFile)
            location = join(installPath, libFile)
            if not isfile(destinationPath):

                # The dependency hasn't been copied over.  So, copy it over,
                # and update library references.
                if not isfile(dependencyPath):
                    raise Exception("dependency '%s' does not exist" %
                                    dependencyPath)
                destinationDir = dirname(destinationPath)
                if not isdir(destinationDir):
                    makedirs(destinationDir)
                stdout.write("Copying library '%s' to '%s' ...\n" %
                             (dependencyPath, destinationPath))
                copyfile(dependencyPath, destinationPath)
                copyLibraries(destinationPath, installPath, libDestinationDir,
                              ignoreDirectories, executablePath)
                setLibraryLocation(destinationPath, location)

        stdout.write("Replacing dependency '%s' with '%s' in '%s' ...\n" %
                     (dependency, location, binaryFile))
        args = ["install_name_tool", "-change", dependency, location,
                binaryFile]
        if call(args):
            raise Exception("error replacing dependency '%s' with '%s' in "
                            "'%s'" % (dependency, location, binaryFile))

def getDependencies(binaryFile):

    """Uses `otool` to get the libraries that the specified binary file (either
       an executable or a dynamic library) depends on.  Returns the list of
       libraries."""

    args = ["otool", "-L", binaryFile]
    process = Popen(args, stdout=PIPE)
    stdoutData, stderrData = process.communicate()
    if process.returncode:
        raise Exception("error getting dependencies for '%s'" % binaryFile)
    dependencies = []
    for s in stdoutData.strip().splitlines()[1:]:
        s = s.strip()
        pos = s.find(" (compatibility ")
        if pos == -1:
            raise Exception("unexpected line output from libtool: '%s'" % s)
        dependencies.append(s[:pos].strip())
    return dependencies

def setLibraryLocation(libraryFile, location):

    """Uses `install_name_tool` to set the id of the given library file to the
       given location."""

    stdout.write("Setting library location of '%s' to '%s' ...\n" %
                 (libraryFile, location))
    args = ["install_name_tool", "-id", location, libraryFile]
    if call(args):
        raise Exception("error setting library location to '%s' for '%s'" %
                        (location, libraryFile))

def main():
    if getPlatform() != PLATFORM_MACX:
        exit("This build script is only meant to be run on Mac OSX")

    parser = OptionParser("usage: %prog package")
    options, args = parser.parse_args()

    # Validate arguments
    if len(args) != 1:
        parser.error("incorrect number of required arguments")
    package = args[0]

    tempDir = mkdtemp()
    try:

        stdout.write("Configuring synthclone for build ...\n")
        appDir = join(tempDir, "synthclone.app")
        contentsDir = join(appDir, "Contents")
        execDir = join(contentsDir, "MacOS")
        frameworksDir = join(contentsDir, "Frameworks")
        pluginsDir = join(contentsDir, "PlugIns")
        args = ["./configure", "--bin-dir=%s" % execDir,
                "--data-dir=%s" % join(contentsDir, "Resources"),
                "--lib-dir=%s" % frameworksDir, "--plugin-dir=%s" % pluginsDir,
                "--skip-api-docs=1", "--skip-headers=1", "--skip-lv2=1",
                "--skip-renoise=1"]
        if call(args):
            raise Exception("configure returned an error")

        stdout.write("Building synthclone ...\n")
        args = ["make"]
        if call(args):
            raise Exception("make returned an error")

        stdout.write("Creating package skeleton in '%s' ...\n" % tempDir)
        args = ["make", "install"]
        if call(args):
            raise Exception("make install returned an error")

        stdout.write("Copying dependencies to package skeleton ...\n")
        if call(["macdeployqt", appDir]):
            parser.error("macdeployqt returned an error")

        stdout.write("Generating Qt dependency map ...\n")
        executablePath = join(contentsDir, "MacOS", "synthclone")
        qtDependencyMap = {}
        for dependency in getDependencies(executablePath):
            baseLibName = basename(dependency)
            if baseLibName.startswith("Qt"):
                qtDependencyMap[baseLibName] = dependency

        stdout.write("Copying plugin dependencies to package skeleton ...\n")
        ignoreDirectories = ["/lib", "/usr/lib", "/Library/Frameworks",
                             "/System/Library/Frameworks"]
        relativeFrameworksPath = "@executable_path/../Frameworks/"
        for f in listdir(pluginsDir):
            plugin = join(pluginsDir, f)
            if not isfile(plugin):
                continue
            setLibraryLocation(plugin, "@executable_path/../PlugIns/%s" % f)
            stdout.write("Copying dependencies for '%s' ...\n" % plugin)
            copyLibraries(plugin, relativeFrameworksPath, frameworksDir,
                          ignoreDirectories, executablePath, qtDependencyMap)

        stdout.write("Copying library dependencies to package skeleton ...\n")
        libPath = join(frameworksDir, "libsynthclone.dylib")
        copyLibraries(libPath, relativeFrameworksPath, frameworksDir,
                      ignoreDirectories, executablePath, qtDependencyMap)

        stdout.write("Generating Info.plist ...\n")
        # XXX: Write me.

        stdout.write("Generating package from skeleton ...\n")
        args = ["hdiutil", "create", "-format", "UDBZ", "-srcfolder", appDir,
                package]
        if call(args):
            parser.error("hdiutil returned an error")

    finally:

        stdout.write("Check %s ...\n" % tempDir)

        # rmtree(tempDir)

    stdout.write("Package '%s' built successfully.\n" % package)

if __name__ == "__main__":
    main()
